package com.moli.iov.handle.real.biz;

import com.moli.iov.cache.RedisUtil;
import com.moli.iov.handle.alarm.model.dto.req.AlarmReqDto;
import com.moli.iov.handle.alarm.service.IAlarmInfoTaskRequestService;
import com.moli.iov.handle.real.model.RealTimeReqDto;
import com.moli.iov.handle.real.service.IVehicleService;
import com.moli.iov.handle.real.service.RealTimeInfoService;
import com.moli.iov.handle.real.util.Contact;
import com.moli.iov.protocol.codec.DataDecoder;
import com.moli.iov.protocol.codec.ReqStreamData;
import com.moli.iov.protocol.enums.CommonLockTag;
import com.moli.iov.protocol.enums.realtime.LocationStateTag;
import com.moli.iov.protocol.enums.realtime.VehicleStateTag;
import com.moli.iov.protocol.model.realtime.*;
import com.moli.iov.real.model.res.VehicleStatusDto;
import com.moli.iov.rest.RestResponse;
import com.moli.iov.util.ByteArrayUtil;
import com.moli.iov.util.GeoUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;

/**
 * @ClassName RealTimeInfoHandler
 * @Description 实时信息处理类
 * @Author 李伟松
 * @CreateDate 2019-02-26 下午 5:09
 * @UpdateDate 2019-02-26 下午 5:09
 * @Version 1.0
 */
@Slf4j
@Service
public class RealTimeInfoHandler implements Handler {

    private static final String SPLIT = "_";

    public static double MILE_XTHRESHOLD = 100D;

    public static int MAX_POINT_DISTANCE = 100; //最大坐标间距

    public static int MAX_ODO = 1000000;//最大公里数（km）

    @Value("${kafka.topic.real.time.biz}")
    private String TOPIC_BIZ_REAL;

    @Resource
    IAlarmInfoTaskRequestService alarmInfoTaskRequestService;

    @Resource
    private ThreadPoolTaskExecutor taskExecutor;

    @Resource
    RealTimeInfoService realTimeInfoService;

    @Resource
    IVehicleService vehicleService;

    @Resource
    RedisUtil redisUtil;

    class RealTimeInfoTmp {
        Date collectDate;
        RealTimeInfoReq req;
        String vin;
        byte[] bytes;

        RealTimeInfoTmp(String vin, RealTimeInfoReq req, Date collectDate, byte[] bytes) {
            this.vin = vin;
            this.req = req;
            this.collectDate = collectDate;
            this.bytes = bytes;
        }
    }

    /**
     * 处理里程异变
     */
    private Boolean handleAlarm(String vin, RealTimeInfoReq req, RealTimeInfoReq oldReq) {
        if (oldReq.getVehicleAllReq() != null && req.getVehicleAllReq() != null &&
                oldReq.collectDate.getDate() != null && req.collectDate.getDate() != null) {
            double currOdo = req.vehicleAllReq.odometer;
            double oldOdo = oldReq.getVehicleAllReq().odometer;


            double odoC = oldOdo - currOdo;
            if (odoC >= MILE_XTHRESHOLD) {
                //通知报警服务产生里程异变
                AlarmReqDto alarmDto = new AlarmReqDto();
                alarmDto.setCollectDate(req.collectDate.getDate());
                alarmDto.setLastReportDate(req.collectDate.getDate());
                alarmDto.setVin(vin);
                alarmDto.setCurrentMileage(currOdo);
                alarmDto.setLastTimeMileage(oldOdo);
                alarmDto.setMilExThreshold(MILE_XTHRESHOLD);

                BigDecimal bg = BigDecimal.valueOf(oldOdo - currOdo);
                double f1 = bg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
                alarmDto.setDifferenceMileage(f1);

                if (req.getVehicleGpsReq() != null) {
                    alarmDto.setLatitude(req.getVehicleGpsReq().latitude);
                    alarmDto.setLongitude(req.getVehicleGpsReq().longitude);
                }

                VehicleStatusDto status = vehicleService.getOdoByVin(vin);
                if (status != null) {
                    alarmDto.setActualMileage(status.getOdo());
                    alarmDto.setGpsMileage(status.getGpsOdo());
                }

                try {
                    RestResponse res = alarmInfoTaskRequestService.mileageExChangeAlarm(alarmDto);
                    log.info("里程异变报警：{}", res);
                } catch (Exception e) {
                    log.error("处理里程异变异常：{}", e);
                }
                return true;
            }
        }
        return false;
    }

    /**
     * 将报文转化为可处理对象
     *
     * @param data
     * @return
     */
    private Map<String, RealTimeInfoTmp> handleRecord(List<ConsumerRecord<String, Object>> data) {

        Map<String, RealTimeInfoTmp> map = new HashMap<>();
        //遍历kafka对象获取报文类
        for (ConsumerRecord<String, Object> record : data) {

            log.info("[实时报文][patition:{}][key:{}][offset:{}]", record.partition(), record.key(), record.offset());
            if (record == null || StringUtils.isEmpty(record.key()) || record.value() == null) {
                continue;
            }

            String vin = record.key();
            try {
                ReqStreamData stream = (ReqStreamData) record.value();
                byte[] bytes = stream.getDataBytes();
                if (stream == null || bytes == null) {
                    continue;
                }
                Date collectDate = stream.getCollectDate();
                RealTimeInfoReq req = new RealTimeInfoReq();
                DataDecoder<RealTimeInfoReq> decoder = new DataDecoder<RealTimeInfoReq>();
                decoder.decode(req, stream.getDataBytes());
                if (collectDate == null && stream.getDataBytes() != null) {
                    try {
                        log.info("上报时间异常：", ByteArrayUtil.toHexString(stream.getDataBytes()));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                //解析报文存入集合 reqMap
                map.put(vin, new RealTimeInfoTmp(vin, req, collectDate, bytes));

            } catch (Exception e) {
                log.error("[处理实时报文异常][vin:{}][patition:{}][{}][offset:{}][value:{}]", vin, record.partition(), record.checksum(), record.offset(), record.value());
            }
        }
        return map;
    }

    /**
     * @Param: [data]
     * @Return: void
     * @Description 消费kafka主函数；
     * 实现思路：
     * 一、遍历kafka对象获取报文类  >>
     * 1.解析报文存入集合 reqMap
     * 2.将需要旧报文赋值的vin存入临时集合 resetVinSet
     * 二、根据临时vin集合查询旧报文
     * 三、遍历reqMap >>
     * 1、符合条件的进行旧报文赋值。
     * 2、填充需要写入缓存的三个map
     * 3、将解析对象写入kafka提供给其他处理服务使用
     * 四、调用写入缓存函数将数据写入缓存
     * @Author 李伟松
     * @CreateDate 2019-02-27 下午 4:38
     */
    @Override
    public void handle(List<ConsumerRecord<String, Object>> data) {

//        taskExecutor.execute(() -> {
        long start = System.currentTimeMillis();

        Map<String, RealTimeInfoTmp> reqMap = handleRecord(data);
        Map<String, RealTimeInfo> oldReqMap = new HashMap<>();
        //根据临时vin集合查询旧报文
        if (!reqMap.isEmpty()) {
            oldReqMap = realTimeInfoService.map(new RealTimeReqDto().put(reqMap.keySet()));
        }

        Map<String, String> locationStateForCacheMap = new HashMap<>();
        Map<String, String> dynamicStateForCacheMap = new HashMap<>();
        List<RealTimeInfo> list = new ArrayList<>();

        //遍历reqMap
        for (Map.Entry<String, RealTimeInfoTmp> entry : reqMap.entrySet()) {

            String vin = entry.getKey();

            RealTimeInfoTmp tmp = entry.getValue();
            RealTimeInfo oldReq = oldReqMap.get(vin);

            //如果存在于需重新赋值的集合内，且对象不为空在对新报文重新赋值
            if (oldReq != null && oldReq.getRealTimeInfoReq() != null) {
                //符合条件的进行旧报文赋值。
                tmp.req = resetRealTimeInfo(tmp.req, oldReq.getRealTimeInfoReq());
//                //里程出现异变
//                handleAlarm(vin,tmp.req,oldReq.getRealTimeInfoReq());
                redisUtil.hset(Contact.KEY_ONLINE_TIME,vin,vin+"_"+System.currentTimeMillis());
                //上报里程过大则视为无效
                if (tmp != null && oldReq.getRealTimeInfoReq().getVehicleAllReq() != null && tmp.req.getVehicleAllReq() != null &&
                        oldReq.getRealTimeInfoReq().collectDate.getDate() != null && tmp.req.collectDate.getDate() != null) {
                    Date currTime = tmp.req.getCollectDate().getDate();
                    Date oldTime = oldReq.getRealTimeInfoReq().getCollectDate().getDate();
                    double currOdo = tmp.req.vehicleAllReq.odometer;
                    double oldOdo = oldReq.getRealTimeInfoReq().getVehicleAllReq().odometer;
                    double odoC = currOdo - oldOdo;
                    long timer = currTime.getTime() - oldTime.getTime();
                    timer = timer / 1000;
                    if (tmp.req.vehicleAllReq.getOdometer() > 500000L) {
                        tmp.req.vehicleAllReq.setOdometer(oldOdo);
                    }

//                    if (timer < odoC ) { //与一秒一公里速度相比，如里程差值仍然较大，视为无效里程，里程保存原有里程
//                        tmp.req.vehicleAllReq.setOdometer(oldOdo);
//                    }else{
                    //里程出现异变
                    handleAlarm(vin, tmp.req, oldReq.getRealTimeInfoReq());
//                    }
                }
            }

            if (tmp.req.getVehicleGpsReq() != null
                    && tmp.req.getVehicleGpsReq().getLocationState() != null) {
                LocationStateTag.IsValid isValid = tmp.req.getVehicleGpsReq().getLocationState().getIsValid();
                if (isValid != null) {
                    locationStateForCacheMap.put(vin, vin + SPLIT + isValid.getValue());
                }
            }

            if (tmp.req.getExtendReq() != null
                    && tmp.req.getExtendReq().getextendVehicleState()!=null) {
                CommonLockTag ds = tmp.req.getExtendReq().getextendVehicleState().getDynamicState();
                if (ds != null) {
                    dynamicStateForCacheMap.put(vin, vin + SPLIT + ds.getV());
                }
            }

            list.add(handleInfo(vin, tmp.req, oldReq));
        }

        //调用写入缓存函数将数据写入缓存
        realTimeInfoService.putCache(locationStateForCacheMap,dynamicStateForCacheMap);
        realTimeInfoService.batchSave(list, oldReqMap.keySet());

        long time = System.currentTimeMillis() - start;
        log.info("[realtime][size:{}][时长:{}ms]", data.size(), time);
//        });
    }

    public RealTimeInfo handleInfo(String vin, RealTimeInfoReq req, RealTimeInfo oldInfo) {
        log.info("处理车辆{}实时报文", vin);
        if (oldInfo == null) {
            log.info("处理车辆{}实时报文,未获取到旧报文");
            return new RealTimeInfo(vin, req);
        }

        RealTimeInfoReq oldReq = oldInfo.getRealTimeInfoReq();

        RealTimeInfo res = new RealTimeInfo();
        res.setVin(vin);
        res.setRealTimeInfoReq(req);

        VehicleGpsReq gpsReq = (req != null ? req.getVehicleGpsReq() : null);
        Date reportDate = req.collectDate.getDate();
        VehicleGpsReq oldGpsReq = (oldReq != null ? oldReq.getVehicleGpsReq() : null);
        VehicleAllReq vehileAll = req.getVehicleAllReq();

        VehicleAllReq oldVehileAll = null;
        if (oldReq != null && oldReq.getVehicleAllReq() != null) {
            oldVehileAll = oldReq.getVehicleAllReq();
        }
        log.info("处理车辆{}实时报文,gps里程", vin);
        if (gpsReq != null) {
            double lat = gpsReq.getLatitude();
            double lng = gpsReq.getLongitude();
            res.setLatitude(lat);
            res.setLongitude(lng);
            res.setGpsReportTime(reportDate);
            if (oldReq != null
                    && oldGpsReq != null
                    && vehileAll != null
                    && vehileAll.getSpeed() > 0) {
                double oldLat = oldGpsReq.getLatitude();
                double oldLng = oldGpsReq.getLongitude();

                //两个坐标间距（km）
                double distanceKm = GeoUtils.getDistance(lat, lng, oldLat, oldLng);
                if (distanceKm < MAX_POINT_DISTANCE) {
                    log.info("0.车架号{}获取计算gps里程：{},{},{},{};计算距离：{},上报时间：{}", vin, lat, lng, oldLat, oldLng, distanceKm, reportDate);
                    if (oldInfo.getGpsOdo() == null) {
                        if (oldInfo.getRealTimeInfoReq() != null
                                && oldInfo.getRealTimeInfoReq().getVehicleAllReq() != null) {
                            BigDecimal b1 = new BigDecimal(Double.toString(oldInfo.getRealTimeInfoReq().getVehicleAllReq().getOdometer()));
                            BigDecimal b2 = new BigDecimal(Double.toString(distanceKm));
                            double gpsOdo = b1.add(b2).doubleValue();
                            res.setGpsOdo(gpsOdo);
                            log.info("1.车架号{}获取计算gps里程：{},上报时间：{}", vin, gpsOdo, reportDate);
                        } else {
                            double gpsOdo = distanceKm;
                            res.setGpsOdo(gpsOdo);
                            log.info("2.车架号{}获取计算gps里程：{},上报时间：{}", vin, gpsOdo, reportDate);
                        }
                    } else {
                        BigDecimal b1 = new BigDecimal(Double.toString(oldInfo.getGpsOdo()));
                        BigDecimal b2 = new BigDecimal(Double.toString(distanceKm));
                        double gpsOdo = b1.add(b2).doubleValue();
                        res.setGpsOdo(gpsOdo);
                        log.info("3.车架号{}获取计算gps里程：{},上报时间：{}", vin, gpsOdo, reportDate);
                    }
                    res.setGpsOdoCalcTime(reportDate);
                } else {
                    log.info("4.车架号{}获取计算gps里程：{},{},{},{};计算距离过大：{},上报时间：{}", vin, lat, lng, oldLat, oldLng, distanceKm, reportDate);
                }

            }
        }

        if (vehileAll != null) {
            if (oldInfo.getMaxOdo() == null
                    || oldInfo.getMaxOdo() < req.getVehicleAllReq().getOdometer()) {
                if (req.getVehicleAllReq().getOdometer() < MAX_ODO) {
                    res.setMaxOdo(req.getVehicleAllReq().getOdometer());
                    res.setMaxOdoReportTime(req.getCollectDate().getDate());
                }
            } else {
                res.setMaxOdo(oldInfo.getMaxOdo());
                res.setMaxOdoReportTime(oldInfo.getMaxOdoReportTime());
            }
            if (oldVehileAll != null) {
                if (oldVehileAll.getOdometer() != vehileAll.getOdometer()) {
                    res.setLastOdoReportTime(reportDate);
                }
            } else {
                res.setLastOdoReportTime(reportDate);
            }
        }

        ExtendVehicleState vehicleState = null;
        if (req != null && req.getExtendReq() != null) {
            vehicleState = req.getExtendReq().getextendVehicleState();
        }
        ExtendVehicleState oldVehicleState = null;
        if (oldReq != null && oldReq.getExtendReq() != null) {
            oldVehicleState = oldReq.getExtendReq().getextendVehicleState();
        }
        if (vehicleState != null
                && oldVehicleState != null
                && vehicleState.getExtendGpsOde() >= 0
                && oldVehicleState.getExtendGpsOde() >= 0
                && (oldVehicleState.getExtendGpsOde() > vehicleState.getExtendGpsOde())) {
            List<Integer> gpsOdoList = oldInfo.getGpsOdoList();
            if (gpsOdoList == null) {
                gpsOdoList = new ArrayList<>();
            }
            gpsOdoList.add(oldVehicleState.getExtendGpsOde());
            res.setGpsOdoList(gpsOdoList);
        }
        if (res.getGpsOdoList() == null && oldInfo != null) {
            res.setGpsOdoList(oldInfo.getGpsOdoList());
        }

        if (res.getGpsOdo() == null) {
            if (oldInfo != null && oldInfo.getGpsOdo() != null) {
                res.setGpsOdo(oldInfo.getGpsOdo());
                res.setGpsOdoCalcTime(oldInfo.getGpsOdoCalcTime());
            } else if (vehileAll != null) {
                res.setGpsOdo(vehileAll.getOdometer());
            }
        }


        return res;
    }

    /**
     * @Param: [tmpReq, oldReqDto]
     * @Return: com.moli.iov.protocol.model.realtime.RealTimeInfoReq
     * @Description 如果新报文部分数据为FF/FE，则赋值为老报文数据
     * @Author 李伟松
     * @CreateDate 2019-02-27 下午 1:34
     */
    private RealTimeInfoReq resetRealTimeInfo(RealTimeInfoReq tmpReq, RealTimeInfoReq oldReqDto) {
        VehicleStateTag tag;
        double odometer = 0;
        byte soc = 0;
        double extendBatteryVoltage = 0;
        byte extendLightState = 0;

        VehicleStateTag oldState = VehicleStateTag.OTHER;
        double oldOdometer = 0;
        byte oldSoc = 0;
        double oldExtendBatteryVoltage = 0;
        byte oldExtendLightState = 0;

        double oldLng = 0;
        double oldLat = 0;

        if (oldReqDto != null) {
            //整车数据需要重新赋值的数值
            VehicleAllReq allReqDto = oldReqDto.getVehicleAllReq();
            if (allReqDto != null) {
                oldOdometer = allReqDto.getOdometer();
                oldState = allReqDto.getVehicleState();
                oldSoc = allReqDto.getSoc();
            }

            //扩展数据需要重新赋值的数值
            ExtendReq extendReq = oldReqDto.getExtendReq();
            if (extendReq != null) {
                ExtendVehicleState extendVehicleState = extendReq.getextendVehicleState();
                if (extendVehicleState != null) {
                    oldExtendBatteryVoltage = extendVehicleState.getExtendBatteryVoltage();
                    oldExtendLightState = extendVehicleState.getExtendLightState();
                }
            }


            VehicleGpsReq vehicleGpsDto = oldReqDto.getVehicleGpsReq();
            if (vehicleGpsDto != null) {
                oldLng = vehicleGpsDto.getLongitude();
                oldLat = vehicleGpsDto.getLatitude();
            }
        }


        if (tmpReq != null) {
            //整车数据需要重新赋值的数值
            VehicleAllReq allReq = tmpReq.getVehicleAllReq();
            if (allReq != null) {
                odometer = allReq.getOdometer();
                if (odometer <= 0) {
                    allReq.setOdometer(oldOdometer);
                }
                tag = allReq.getVehicleState();
                if (tag == null
                        || tag.getK().equals(VehicleStateTag.ERROR.getK())
                        || tag.getK().equals(VehicleStateTag.INVALID.getK())) {
                    if (oldState != null) {
                        allReq.setVehicleState(oldState);
                    }

                }
                if (soc < 0) {
                    allReq.setSoc(oldSoc);
                }

            } else {
                if (oldReqDto.getVehicleAllReq() != null) {
                    allReq = new VehicleAllReq();
                    allReq.setOdometer(oldReqDto.getVehicleAllReq().getOdometer());
                    tmpReq.setVehicleAllReq(allReq);
                }
            }

            //扩展数据需要重新赋值的数值
            ExtendReq extendReq = tmpReq.getExtendReq();
            if (extendReq != null) {
                ExtendVehicleState extendVehicleState = extendReq.getextendVehicleState();
                if (extendVehicleState != null) {
                    extendBatteryVoltage = extendVehicleState.getExtendBatteryVoltage();
                    if (extendBatteryVoltage == -98 || extendBatteryVoltage == -99) {
                        extendVehicleState.setBatteryVoltage(oldExtendBatteryVoltage);
                    }
                    if (extendLightState == -98 || extendLightState == -99) {
                        extendVehicleState.setExtendLightState(oldExtendLightState);
                    }

                }
            }

            VehicleGpsReq gpsReq = tmpReq.getVehicleGpsReq();
            if (gpsReq != null) {
                if (gpsReq.getLongitude() < 72
                        || gpsReq.getLongitude() > 136
                        || gpsReq.getLatitude() < 2
                        || gpsReq.getLatitude() > 54) {
                    gpsReq.setLongitude(oldLng);
                    gpsReq.setLongitude(oldLat);
                }
            } else {
                if (oldReqDto != null && oldReqDto.getVehicleGpsReq() != null) {
                    gpsReq = new VehicleGpsReq();
                    VehicleGpsReq gpsDto = oldReqDto.getVehicleGpsReq();
                    gpsReq.setLocationState(gpsDto.getLocationState());
                    gpsReq.setLatitude(gpsDto.getLatitude());
                    gpsReq.setLongitude(gpsDto.getLongitude());
                    tmpReq.setVehicleGpsReq(gpsReq);
                }
            }
        }
        return tmpReq;
    }

    /**
     * @Param: [req]
     * @Return: boolean
     * @Description 判断是否需要对上报数据进行处理
     * @Author 李伟松
     * @CreateDate 2019-02-26 下午 5:51
     */
    private boolean checkRealTimeInfoReq(RealTimeInfoReq req) {
        VehicleAllReq allReq = req.getVehicleAllReq();
        if (allReq != null) {
            if (allReq.getSoc() == -98
                    || allReq.getSoc() == -99
                    || allReq.getOdometer() <= 0
                    || allReq.getVehicleState() == null
                    || allReq.getVehicleState().equals(VehicleStateTag.ERROR)
                    || allReq.getVehicleState().equals(VehicleStateTag.INVALID)) {
                return true;
            }
        } else {
            return true;
        }

        if (req.getExtendReq() != null) {
            ExtendVehicleState vehicleState = req.getExtendReq().getextendVehicleState();
            if (vehicleState != null) {
                if (vehicleState.getExtendBatteryVoltage() == -98
                        || vehicleState.getExtendBatteryVoltage() == -99
                        || vehicleState.getExtendLightState() == -98
                        || vehicleState.getExtendLightState() == -99) {
                    return true;
                }
            }
        }

        if (req.getVehicleGpsReq() == null
                || req.getVehicleGpsReq().getLongitude() < 72
                || req.getVehicleGpsReq().getLatitude() > 136) {
            return true;
        }
        return false;
    }
}
